! Copyright (C) 2005, 2009 Daniel Ehrenberg
! See http://factorcode.org/license.txt for BSD license.
USING: words assocs kernel accessors parser vocabs.parser
effects.parser sequences summary lexer splitting combinators
locals memoize sequences.deep xml.data xml.state xml namespaces
present arrays generalizations sequences.generalizations strings
make math macros multiline combinators.short-circuit sorting fry
unicode effects ;
IN: xml.syntax

<PRIVATE

ERROR: no-tag name word ;

M: no-tag summary
    drop "The tag-dispatching word has no method for the given tag name" ;

: compile-tags ( word xtable -- quot )
    >alist swap '[ _ no-tag ] suffix '[ dup main>> _ case ] ;

: define-tags ( word effect -- )
    [ dup dup "xtable" word-prop compile-tags ] dip define-declared ;

:: define-tag ( string word quot -- )
    quot string word "xtable" word-prop set-at
    word word stack-effect define-tags ;

PRIVATE>

SYNTAX: TAGS:
    scan-new-word scan-effect
    [ drop H{ } clone "xtable" set-word-prop ]
    [ define-tags ]
    2bi ;

SYNTAX: TAG:
    scan-token scan-word parse-definition define-tag ;

SYNTAX: XML-NS:
    scan-new-word scan-token '[ f swap _ <name> ] ( string -- name ) define-memoized ;

<PRIVATE

: each-attrs ( attrs quot -- )
    [ values [ interpolated? ] filter ] dip each ; inline

: (each-interpolated) ( ... item quot: ( ... interpolated -- ... ) -- ... )
     {
        { [ over interpolated? ] [ call ] }
        { [ over tag? ] [ [ attrs>> ] dip each-attrs ] }
        { [ over attrs? ] [ each-attrs ] }
        { [ over xml? ] [ [ body>> ] dip (each-interpolated) ] }
        [ 2drop ]
     } cond ; inline recursive

: each-interpolated ( xml quot -- )
    '[ _ (each-interpolated) ] deep-each ; inline

: has-interpolated? ( xml -- ? )
    ! If this becomes a performance problem, it can be improved
    f swap [ 2drop t ] each-interpolated ;

: when-interpolated ( xml quot -- genquot )
    [ dup has-interpolated? ] dip [ '[ _ swap ] ] if ; inline

: string>chunk ( string -- chunk )
    t interpolating? [ string>xml-chunk ] with-variable ;

: string>doc ( string -- xml )
    t interpolating? [ string>xml ] with-variable ;

DEFER: interpolate-sequence

: get-interpolated ( interpolated -- quot )
    var>> '[ [ _ of ] keep ] ;

: ?present ( object -- string )
    dup [ present ] when ;

: interpolate-attr ( key value -- quot )
    dup interpolated?
    [ get-interpolated '[ _ swap @ [ ?present 2array ] dip ] ]
    [ 2array '[ _ swap ] ] if ;

: interpolate-attrs ( attrs -- quot )
    [
        [ [ interpolate-attr ] { } assoc>map [ ] join ]
        [ assoc-size ] bi
        '[ @ _ swap [ narray sift-values <attrs> ] dip ]
    ] when-interpolated ;

: interpolate-tag ( tag -- quot )
    [
        [ name>> ]
        [ attrs>> interpolate-attrs ]
        [ children>> interpolate-sequence ] tri
        '[ _ swap @ @ [ <tag> ] dip ]
    ] when-interpolated ;

GENERIC: push-item ( item -- )
M: string push-item , ;
M: xml-data push-item , ;
M: object push-item present , ;
M: sequence push-item
    dup xml-data? [ , ] [ [ push-item ] each ] if ;
M: xml push-item
    [ before>> push-item ]
    [ body>> push-item ]
    [ after>> push-item ] tri ;
M: number push-item present , ;
M: xml-chunk push-item % ;

: concat-interpolate ( array -- newarray )
    [ [ push-item ] each ] { } make ;

GENERIC: interpolate-item ( item -- quot )
M: object interpolate-item [ swap ] curry ;
M: tag interpolate-item interpolate-tag ;
M: interpolated interpolate-item get-interpolated ;

: interpolate-sequence ( seq -- quot )
    [
        [ [ interpolate-item ] map concat ]
        [ length ] bi
        '[ @ _ swap [ narray concat-interpolate ] dip ]
    ] when-interpolated ;

GENERIC: [interpolate-xml] ( xml -- quot )

M: xml [interpolate-xml]
    dup body>> interpolate-tag
    '[ _ (clone) swap @ drop >>body ] ;

M: xml-chunk [interpolate-xml]
    interpolate-sequence
    '[ @ drop <xml-chunk> ] ;

MACRO: interpolate-xml ( xml -- quot )
    [interpolate-xml] ;

: number<-> ( doc -- dup )
    0 over [
        dup var>> [
            over >>var [ 1 + ] dip
        ] unless drop
    ] each-interpolated drop ;

: >search-hash ( seq -- hash )
    [ dup parse-word ] H{ } map>assoc ;

: extract-variables ( xml -- seq )
    [ [ var>> , ] each-interpolated ] { } make ;

: nenum ( ... n -- assoc )
    narray <enumerated> ; inline

: collect ( accum variables -- accum ? )
    {
        { [ dup empty? ] [ drop f ] } ! Just a literal
        { [ dup [ ] all? ] [ >search-hash suffix! t ] } ! locals
        { [ dup [ not ] all? ] [ length suffix! \ nenum suffix! t ] } ! fry
        [ drop "XML interpolation contains both fry and locals" throw ] ! mixed
    } cond ;

: parse-def ( accum delimiter quot -- accum )
    [ parse-multiline-string [ blank? ] trim ] dip call
    [ extract-variables collect ] keep swap
    [ number<-> suffix! ] dip
    [ \ interpolate-xml suffix! ] when ; inline

PRIVATE>

SYNTAX: <XML
    "XML>" [ string>doc ] parse-def ;

SYNTAX: [XML
    "XML]" [ string>chunk ] parse-def ;

USE: vocabs.loader

{ "xml.syntax" "inverse" } "xml.syntax.inverse" require-when
